﻿using Irony.Parsing;

namespace Irony.Samples.Json
{


    [Language("JSON", "1.0", "JSON data format")]
    public class ExtJSGrammar : Grammar
    {
        public ExtJSGrammar()
        {

            RegisterOperators(10, "*", "/", "%");
            RegisterOperators(9, "+", "-");
            RegisterOperators(8, "=", ">", "<", ">=", "<=", "<>", "!=", "!<", "!>", "LIKE", "IN");
            RegisterOperators(7, "^", "&", "|");
            RegisterOperators(5, "AND");
            RegisterOperators(4, "OR");

            //Terminals
            var str = new StringLiteral("string");
            str.AddStartEnd("'", StringOptions.AllowsAllEscapes);
            str.AddStartEnd("\"", StringOptions.AllowsAllEscapes);

            var regexBasedTerminal = new RegexBasedTerminal("name", "[A-Za-z][A-Za-z0-9-_]*");
            var stringTerminal = new StringLiteral("string");
            stringTerminal.AddStartEnd("'", "'", StringOptions.None);
            stringTerminal.AddStartEnd("\"", "\"", StringOptions.AllowsAllEscapes);

            var rootNonTerminal = new NonTerminal("Root");
            var singleLineNonTerminal = new NonTerminal("SingleLineRoot");
            var commentNonTerminal = new NonTerminal("Comment");
            var singleOperationNonTerminal = new NonTerminal("SingleOperation");
            var objectAccessPathNonTerminal = new NonTerminal("ObjectAccessPath");  //like a.b.c
            var objectAccessPathSingleElementNonTerminal = new NonTerminal("ObjectAccessPathSingleElement"); //like b

            var objectAccessPathSingleElementParametrsNonTerminal = new NonTerminal("ObjectAccessPathSingleElementParametrs"); //like [1] or (a, b)
            var objectAccessPathSingleElementParametrsParametrListNonTerminal = new NonTerminal("ObjectAccessPathSingleElementParametrList"); //like [1] or (a, b)
            var objectAccessPathSingleElementParametrsOneParametrNonTerminal = new NonTerminal("ObjectAccessPathSingleElementOneParametr"); //like [1] or (a, b)

            var objectNonTerminal = new NonTerminal("Object"); //like {}

            var objectElementsNonTerminal = new NonTerminal("ObjectElements"); //like {a:1,b:2}
            var objectElementNonTerminal = new NonTerminal("ObjectElementValue"); //like a:1
            var objectElementValueNonTerminal = new NonTerminal("ObjectElement"); //like a:1

            var functionNonTerminal = new NonTerminal("Function"); //like function a(){}

            var arrayNonTerminal = new NonTerminal("Array"); //like [a,b,c]
            var arrayElementListNonTerminal = new NonTerminal("ArrayElement"); //like [a,b,c]

            var booleanStatementNonTerminal = new NonTerminal("booleanStatement");

            var booleanStatementElementNonTerminal = new NonTerminal("booleanStatementElement");
            var booleanStatementConnectorNonTerminal = new NonTerminal("booleanStatementConnector");
            var booleanStatementAssignNonTerminal = new NonTerminal("booleanStatementAssign");




            var ifNonTerminal = new NonTerminal("if");
            var ifBodyNonTerminal = new NonTerminal("ifBody");
            var elseNonTerminal = new NonTerminal("else");
            var elseBodyNonTerminal = new NonTerminal("elseBody");


            var varNonTerminal = new NonTerminal("var");


            var forNonTerminal = new NonTerminal("for");
            var forBodyNonTerminal = new NonTerminal("forBody");



            commentNonTerminal.Rule = new CommentTerminal("MultiLineComment", "/*", "*/") | new CommentTerminal("Comment", "//", "\n");


            rootNonTerminal.Rule = MakeStarRule(rootNonTerminal, ToTerm(";").Q(), singleLineNonTerminal);

            singleLineNonTerminal.Rule = singleOperationNonTerminal | arrayNonTerminal | varNonTerminal | stringTerminal | objectNonTerminal | functionNonTerminal | commentNonTerminal | ifNonTerminal | ToKeyTerm("||") | ToKeyTerm("=") | new NumberLiteral("int") | ToTerm("(") + singleLineNonTerminal + ToTerm(")") | forNonTerminal;

            singleOperationNonTerminal.Rule = objectAccessPathNonTerminal;

            objectAccessPathNonTerminal.Rule = MakeStarRule(
                objectAccessPathNonTerminal, ToTerm("."), objectAccessPathSingleElementNonTerminal);

            objectAccessPathSingleElementNonTerminal.Rule = regexBasedTerminal + arrayNonTerminal.Q() + objectAccessPathSingleElementParametrsNonTerminal.Q();

            objectAccessPathSingleElementParametrsNonTerminal.Rule = ToKeyTerm("(")
                                                                     +
                                                                     objectAccessPathSingleElementParametrsParametrListNonTerminal
                                                                     + ToKeyTerm(")");

            objectAccessPathSingleElementParametrsParametrListNonTerminal.Rule =
                MakeStarRule(
                    objectAccessPathSingleElementParametrsParametrListNonTerminal,
                    ToTerm(","),
                    objectAccessPathSingleElementParametrsOneParametrNonTerminal);

            objectAccessPathSingleElementParametrsOneParametrNonTerminal.Rule = singleLineNonTerminal;

            objectNonTerminal.Rule = ToKeyTerm("{") + objectElementsNonTerminal.Q() + ToKeyTerm("}");

            objectElementsNonTerminal.Rule = MakeStarRule(
                objectElementNonTerminal, ToTerm(","), objectElementNonTerminal);
            objectElementNonTerminal.Rule = objectElementValueNonTerminal;
            objectElementValueNonTerminal.Rule = regexBasedTerminal + ToTerm(":") + singleLineNonTerminal;

            this.Root = rootNonTerminal;

            functionNonTerminal.Rule = ToKeyTerm("function") + objectAccessPathSingleElementParametrsNonTerminal
                                       + ToKeyTerm("{") + rootNonTerminal + ToKeyTerm("}");

            arrayNonTerminal.Rule = ToKeyTerm("[") + arrayElementListNonTerminal.Q() + ToKeyTerm("]");

            arrayElementListNonTerminal.Rule = MakeStarRule(arrayElementListNonTerminal, ToTerm(","), singleLineNonTerminal);


            ifNonTerminal.Rule = ToKeyTerm("if") + ToKeyTerm("(") + booleanStatementNonTerminal + ToKeyTerm(")") + ifBodyNonTerminal + elseNonTerminal.Q();
            ifBodyNonTerminal.Rule = ToKeyTerm("{") + rootNonTerminal + ToKeyTerm("}") | singleLineNonTerminal;
            elseNonTerminal.Rule = ToKeyTerm("else") + elseBodyNonTerminal;
            elseBodyNonTerminal.Rule = ToKeyTerm("{") + rootNonTerminal + ToKeyTerm("}") | singleLineNonTerminal;



            booleanStatementNonTerminal.Rule = booleanStatementAssignNonTerminal | booleanStatementConnectorNonTerminal
                                       | ToKeyTerm("(") + booleanStatementAssignNonTerminal + ToKeyTerm(")") | ToKeyTerm("false") | ToKeyTerm("true");
            booleanStatementConnectorNonTerminal.Rule = booleanStatementConnectorNonTerminal + ToKeyTerm("||") + booleanStatementAssignNonTerminal | booleanStatementConnectorNonTerminal + ToKeyTerm("&&") + booleanStatementAssignNonTerminal | booleanStatementAssignNonTerminal;
            booleanStatementAssignNonTerminal.Rule = booleanStatementElementNonTerminal + ToKeyTerm("==")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm("===")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm("!=")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm("!==")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm(">=")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm("<=")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm("<")
                                                     + booleanStatementElementNonTerminal
                                                     |
                                                     booleanStatementElementNonTerminal + ToKeyTerm(">")
                                                     + booleanStatementElementNonTerminal

                                                     |
                                                     booleanStatementElementNonTerminal;

            booleanStatementElementNonTerminal.Rule = ToKeyTerm("!").Q() + ToKeyTerm("typeof").Q() + singleLineNonTerminal;


            varNonTerminal.Rule = ToKeyTerm("var").Q() + regexBasedTerminal + ToKeyTerm("=")
                                  + singleLineNonTerminal | varNonTerminal + ToKeyTerm(",") + varNonTerminal | ToKeyTerm("var").Q() + regexBasedTerminal;

            var forElements = new NonTerminal("forElement");
            var forSingleElement = new NonTerminal("forElement");
            forElements.Rule = MakeStarRule(forSingleElement, forElements);


            forSingleElement.Rule = singleLineNonTerminal | booleanStatementNonTerminal;

            forNonTerminal.Rule = ToKeyTerm("for") + ToKeyTerm("(") + regexBasedTerminal + ToKeyTerm("in") + singleLineNonTerminal + ToKeyTerm(")") + forBodyNonTerminal
                | ToKeyTerm("for") + ToKeyTerm("(") + singleLineNonTerminal + ToKeyTerm("=").Q() + singleLineNonTerminal.Q() + ToKeyTerm(";") + booleanStatementNonTerminal.Q() + ToKeyTerm(";") + singleLineNonTerminal.Q() + ToKeyTerm(")") + forBodyNonTerminal;
            forBodyNonTerminal.Rule = ToKeyTerm("{") + rootNonTerminal + ToKeyTerm("}") | singleLineNonTerminal;


        }//constructor


        KeyTerm ToKeyTerm(string s)
        {
            var r = ToTerm(s);
            r.SetFlag(TermFlags.IsReservedWord);
            r.Name = s;
            return r;
        }
    }//class
}//namespace